from genericpath import exists
import os
import json
import subprocess
import shutil
from datetime import datetime
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QTreeWidget, QTreeWidgetItem, 
                            QPushButton, QHeaderView, QMenu, QDialog, QLabel, QMessageBox, QHBoxLayout, QTabWidget, QTextEdit)
from PyQt5.QtCore import Qt, QThread, pyqtSignal  # 新增
from config.config import DEFAULT_INTERVAL_SEC  # 新增配置导入
from core.image_processor.extract_frame import extract_frames  # 新增抽帧函数导入
from PyQt5.QtGui import QIcon, QColor
import cv2
import glob
import numpy as np
from collections import defaultdict
from PyQt5.QtWidgets import (QFileDialog, QSpinBox, QCheckBox, QComboBox, QGroupBox, QProgressBar, QListWidget, QListWidgetItem)
from core.alignment.mmaligment import center_crop_resize
from core.utils.image_utils import cv2_to_qpixmap

VIDEO_EXTS = ('.mp4', '.avi', '.mov')

class FrameExtractionThread(QThread):
    extraction_finished = pyqtSignal(bool, str)  # True表示成功，False表示失败，错误信息
    def __init__(self, video_path, extraction_folder, interval_sec, parent=None):
        super().__init__(parent)
        self.video_path = video_path
        self.extraction_folder = extraction_folder
        self.interval_sec = interval_sec

    def run(self):
        try:
            extract_frames(self.video_path, self.extraction_folder, interval_sec=self.interval_sec)
            self.extraction_finished.emit(True, "")
        except Exception as e:
            self.extraction_finished.emit(False, str(e))


class AnnotationScreenshotThread(QThread):
    """用于生成标注截图的线程"""
    progress_update = pyqtSignal(int, int)  # 当前进度, 总数
    finished_signal = pyqtSignal(str)  # 完成信息
    error_signal = pyqtSignal(str)  # 错误信息

    def __init__(self, video_path, colors_map, align_images=True, find_frame_folder_func=None):
        """
        初始化截图线程
        :param video_path: 视频文件路径
        :param colors_map: 颜色映射（ID到颜色的字典）
        :param align_images: 是否对齐RGB和TI图像
        :param find_frame_folder_func: 查找帧文件夹的函数引用
        """
        super().__init__()
        self.video_path = video_path
        self.colors_map = colors_map
        self.align_images = align_images
        self.stop_flag = False
        self.find_frame_folder_func = find_frame_folder_func

    # Add this method to the AnnotationScreenshotThread class
    def _get_frame_metadata(self, metadata_dict, frame_idx):
        """从元数据字典中获取指定帧的元数据"""
        try:
            # 检查元数据字典是否有效
            if not metadata_dict or not isinstance(metadata_dict, dict):
                return {}
                
            # 首先尝试从frames数组中获取
            frames = metadata_dict.get("frames", [])
            if frames and isinstance(frames, list) and frame_idx < len(frames):
                return frames[frame_idx]
                
            # 如果没有frames数组 or 索引无效，尝试查找frame_{idx}格式的键
            frame_key = f"frame_{frame_idx}"
            if frame_key in metadata_dict:
                return metadata_dict[frame_key]
                
            # 作为最后尝试，直接返回整个元数据字典的副本
            # 但排除frames数组以避免过大
            result = metadata_dict.copy()
            if "frames" in result:
                del result["frames"]
            return result
            
        except Exception as e:
            print(f"获取帧元数据出错: {str(e)}")
            return {}

    def run(self):
        try:
            import os
            import glob
            import traceback
            import numpy as np
            import cv2
            from collections import defaultdict
            
            # 解析视频路径，找到对应帧图像和标注文件
            video_dir = os.path.dirname(self.video_path)
            video_name = os.path.basename(self.video_path)
            
            # 从视频路径解析帧文件夹路径
            if self.find_frame_folder_func:
                frame_group_folder = self.find_frame_folder_func(self.video_path)
            else:
                # 直接实现查找逻辑作为备选
                frame_group_folder = self._default_find_frame_folder(self.video_path)
                
            if not frame_group_folder:
                self.error_signal.emit(f"无法找到视频 {video_name} 对应的帧文件夹")
                return
            
            # 查找V和T子文件夹
            v_folder = os.path.join(frame_group_folder, "V")
            t_folder = os.path.join(frame_group_folder, "T")
            
            if not os.path.exists(v_folder):
                v_folder = None
            if not os.path.exists(t_folder):
                t_folder = None
                
            if not v_folder and not t_folder:
                self.error_signal.emit(f"未找到有效的RGB or TI帧文件夹")
                return
            
            # 分别找到RGB(V)和TI(T)的标注文件
            v_annotation_file = None
            t_annotation_file = None
            
            if v_folder and os.path.exists(os.path.join(v_folder, "annotations.json")):
                v_annotation_file = os.path.join(v_folder, "annotations.json")
            
            if t_folder and os.path.exists(os.path.join(t_folder, "annotations.json")):
                t_annotation_file = os.path.join(t_folder, "annotations.json")
            
            if not v_annotation_file and not t_annotation_file:
                self.error_signal.emit("未找到annotations.json文件，无法生成标注截图")
                return
            
            # 创建RGB和TI截图保存目录
            v_crop_folder = None
            t_crop_folder = None
            
            if v_folder:
                v_crop_folder = os.path.join(v_folder, "crop")
                os.makedirs(v_crop_folder, exist_ok=True)
                
            if t_folder:
                t_crop_folder = os.path.join(t_folder, "crop")
                os.makedirs(t_crop_folder, exist_ok=True)
                
            # 读取所有帧文件
            rgb_frames = sorted(glob.glob(os.path.join(v_folder, "*.jpg"))) if v_folder else []
            ti_frames = sorted(glob.glob(os.path.join(t_folder, "*.jpg"))) if t_folder else []
            
            # 读取并解析RGB标注
            v_annotations = []
            if v_annotation_file:
                try:
                    import json
                    print(f"尝试加载RGB标注文件: {v_annotation_file}")
                    with open(v_annotation_file, 'r', encoding='utf-8') as f:
                        raw_data = f.read()
                        data = json.loads(raw_data)
                        if isinstance(data, dict) and "annotations" in data:
                            v_annotations = data["annotations"]
                            print(f"读取到 {len(v_annotations)} 个RGB标注")
                        else:
                            print(f"RGB标注文件格式无效")
                except Exception as e:
                    print(f"读取RGB标注文件出错: {str(e)}")
            
            # 读取并解析TI标注
            t_annotations = []
            if t_annotation_file:
                try:
                    import json
                    print(f"尝试加载TI标注文件: {t_annotation_file}")
                    with open(t_annotation_file, 'r', encoding='utf-8') as f:
                        raw_data = f.read()
                        data = json.loads(raw_data)
                        if isinstance(data, dict) and "annotations" in data:
                            t_annotations = data["annotations"]
                            print(f"读取到 {len(t_annotations)} 个TI标注")
                        else:
                            print(f"TI标注文件格式无效")
                except Exception as e:
                    print(f"读取TI标注文件出错: {str(e)}")
            
            # 按帧索引组织标注
            rgb_annotations_by_frame = defaultdict(list)
            for ann in v_annotations:
                if isinstance(ann, dict) and "frame" in ann and "bbox" in ann and len(ann.get("bbox", [])) == 4:
                    frame = ann.get("frame")
                    if frame is not None:
                        rgb_annotations_by_frame[frame].append(ann)
            
            ti_annotations_by_frame = defaultdict(list)
            for ann in t_annotations:
                if isinstance(ann, dict) and "frame" in ann and "bbox" in ann and len(ann.get("bbox", [])) == 4:
                    frame = ann.get("frame")
                    if frame is not None:
                        ti_annotations_by_frame[frame].append(ann)
            
            # 获取所有有标注的帧
            frames_to_process = set(rgb_annotations_by_frame.keys()) | set(ti_annotations_by_frame.keys())
                
            if not frames_to_process:
                self.error_signal.emit("未找到任何有标注的帧，无法生成截图")
                return
                
            # 按帧号排序
            frames_to_process = sorted(list(frames_to_process))
            print(f"找到 {len(frames_to_process)} 个有标注的帧: {frames_to_process}")
            
            # 开始处理帧
            total = len(frames_to_process)
            processed_count = 0
            success_count = 0
            total_crops = 0
            
            for frame_idx in frames_to_process:
                if self.stop_flag:
                    self.error_signal.emit("处理被用户中断")
                    return
                
                try:
                    # 获取对应的RGB和TI图像路径
                    rgb_path = rgb_frames[frame_idx] if frame_idx < len(rgb_frames) and rgb_frames else None
                    ti_path = ti_frames[frame_idx] if frame_idx < len(ti_frames) and ti_frames else None
                    
                    print(f"处理第 {frame_idx+1} 帧 (索引: {frame_idx}):")
                    print(f"  RGB路径: {rgb_path}")
                    print(f"  TI路径: {ti_path}")
                    
                    # 获取该帧的所有标注
                    rgb_frame_annotations = rgb_annotations_by_frame.get(frame_idx, [])
                    ti_frame_annotations = ti_annotations_by_frame.get(frame_idx, [])
                    
                    print(f"  RGB标注数量: {len(rgb_frame_annotations)}")
                    print(f"  TI标注数量: {len(ti_frame_annotations)}")
                    
                    # 处理该帧的标注和生成截图
                    crops_count = self._generate_crops_for_frame(
                        rgb_path, ti_path, frame_idx, 
                        rgb_frame_annotations, ti_frame_annotations, 
                        v_crop_folder, t_crop_folder
                    )
                    
                    if crops_count > 0:
                        success_count += 1
                        total_crops += crops_count
                        print(f"  成功生成第 {frame_idx+1} 帧的 {crops_count} 个标注截图")
                    else:
                        print(f"  第 {frame_idx+1} 帧未生成任何截图")
                except Exception as frame_error:
                    print(f"处理第 {frame_idx+1} 帧时出错: {str(frame_error)}")
                    import traceback
                    print(traceback.format_exc())
                
                processed_count += 1
                self.progress_update.emit(processed_count, total)
            
            # 准备输出消息 - 显示RGB和TI截图保存位置
            crop_locations = []
            if v_crop_folder:
                crop_locations.append(f"RGB截图: {v_crop_folder}")
            if t_crop_folder:
                crop_locations.append(f"TI截图: {t_crop_folder}")
            crop_locations_str = "，".join(crop_locations)
            
            self.finished_signal.emit(f"成功处理 {success_count}/{total} 帧，生成 {total_crops} 张标注区域截图。\n保存在：{crop_locations_str}")
            
        except Exception as e:
            self.error_signal.emit(f"生成截图出错: {str(e)}\n{traceback.format_exc()}")

    def _generate_crops_for_frame(self, rgb_path, ti_path, frame_idx, rgb_annotations, ti_annotations, v_crop_folder, t_crop_folder):
        """为单个帧生成标注区域的裁剪图像"""
        try:
            import cv2
            import numpy as np
            import os
            import json
            
            crops_count = 0
            
            # 用于每个ID收集元数据的字典
            rgb_id_metadata = {}  # color_id -> {screenshot_name: metadata}
            ti_id_metadata = {}
            
            # 加载RGB和TI目录的metadata.json文件
            rgb_metadata = {}
            ti_metadata = {}
            
            if rgb_path:
                # 获取RGB帧所在的文件夹路径
                v_folder = os.path.dirname(rgb_path)
                metadata_path = os.path.join(v_folder, "metadata.json")
                
                # 也尝试上级目录的metadata.json
                if not os.path.exists(metadata_path):
                    parent_folder = os.path.dirname(v_folder)
                    metadata_path = os.path.join(parent_folder, "metadata.json")
                    
                if os.path.exists(metadata_path):
                    try:
                        with open(metadata_path, 'r', encoding='utf-8') as f:
                            rgb_metadata = json.load(f)
                        print(f"  加载RGB元数据文件: {metadata_path}")
                    except Exception as e:
                        print(f"  读取RGB元数据文件出错: {str(e)}")
            
            if ti_path:
                # 获取TI帧所在的文件夹路径
                t_folder = os.path.dirname(ti_path)
                metadata_path = os.path.join(t_folder, "metadata.json")
                
                # 也尝试上级目录的metadata.json
                if not os.path.exists(metadata_path):
                    parent_folder = os.path.dirname(t_folder)
                    metadata_path = os.path.join(parent_folder, "metadata.json")
                    
                if os.path.exists(metadata_path):
                    try:
                        with open(metadata_path, 'r', encoding='utf-8') as f:
                            ti_metadata = json.load(f)
                        print(f"  加载TI元数据文件: {metadata_path}")
                    except Exception as e:
                        print(f"  读取TI元数据文件出错: {str(e)}")
                        
            # 获取当前帧的元数据
            rgb_frame_metadata = self._get_frame_metadata(rgb_metadata, frame_idx)
            ti_frame_metadata = self._get_frame_metadata(ti_metadata, frame_idx)
            
            # 处理RGB图像标注
            if rgb_path and os.path.exists(rgb_path) and rgb_annotations and v_crop_folder:
                rgb_img = cv2.imread(rgb_path)
                if rgb_img is not None:
                    print(f"  加载RGB图像: {rgb_path}, 尺寸: {rgb_img.shape}")
                    
                    for i, ann in enumerate(rgb_annotations):
                        try:
                            # 获取边界框
                            bbox = ann.get("bbox")
                            if not bbox or len(bbox) != 4:
                                print(f"    跳过RGB标注 #{i}: 无效bbox {bbox}")
                                continue
                                
                            color_id = ann.get("color_id", "unknown")
                            # 确保bbox是整数，处理浮点数bbox
                            x, y, w, h = [int(float(val)) for val in bbox]
                            
                            print(f"    处理RGB标注 #{i}: ID={color_id}, bbox=({x},{y},{w},{h})")
                            
                            # 确保边界框在图像内
                            if x < 0 or y < 0 or x + w > rgb_img.shape[1] or y + h > rgb_img.shape[0]:
                                print(f"    警告: RGB标注 #{i} bbox超出图像边界，将进行裁剪")
                                
                            x = max(0, x)
                            y = max(0, y)
                            w = min(w, rgb_img.shape[1] - x)
                            h = min(h, rgb_img.shape[0] - y)
                            
                            if w <= 0 or h <= 0:
                                print(f"    跳过RGB标注 #{i}: 裁剪后bbox无效 ({x},{y},{w},{h})")
                                continue
                                
                            # 裁剪图像 - 不添加文本水印
                            crop_img = rgb_img[y:y+h, x:x+w].copy()
                            print(f"    RGB裁剪成功，尺寸: {crop_img.shape}")
                            
                            # 创建按ID组织的子文件夹
                            id_folder = os.path.join(v_crop_folder, f"id_{color_id}")
                            os.makedirs(id_folder, exist_ok=True)
                            
                            # 保存裁剪的图像到ID子文件夹中
                            out_name = f"frame_{frame_idx+1}_rgb_id{color_id}_{i}.jpg"
                            output_path = os.path.join(id_folder, out_name)
                            cv2.imwrite(output_path, crop_img)
                            print(f"  保存RGB截图: {out_name} - 尺寸: {crop_img.shape}")
                            
                            # 收集该截图的元数据
                            if color_id not in rgb_id_metadata:
                                rgb_id_metadata[color_id] = {}
                            
                            # 添加元数据，包括bbox和完整的帧元数据
                            metadata_entry = {
                                "frame": frame_idx,
                                "frame_path": rgb_path,
                                "bbox": [x, y, w, h],
                                "modality": "RGB",
                                "color_id": color_id,
                                "original_annotation": ann
                            }
                            
                            # 添加从metadata.json中解析的所有帧元数据
                            if rgb_frame_metadata:
                                metadata_entry.update(rgb_frame_metadata)
                            
                            rgb_id_metadata[color_id][out_name] = metadata_entry
                            
                            crops_count += 1
                        except Exception as e:
                            print(f"  处理RGB标注时出错: {str(e)}")
                            import traceback
                            print(traceback.format_exc())
            
            # 处理TI图像标注
            if ti_path and os.path.exists(ti_path) and ti_annotations and t_crop_folder:
                ti_img = cv2.imread(ti_path)
                if ti_img is not None:
                    print(f"  加载TI图像: {ti_path}, 尺寸: {ti_img.shape}")
                    
                    for i, ann in enumerate(ti_annotations):
                        try:
                            # 获取边界框
                            bbox = ann.get("bbox")
                            if not bbox or len(bbox) != 4:
                                print(f"    跳过TI标注 #{i}: 无效bbox {bbox}")
                                continue
                                
                            color_id = ann.get("color_id", "unknown")
                            # 确保bbox是整数，处理浮点数bbox
                            x, y, w, h = [int(float(val)) for val in bbox]
                            
                            print(f"    处理TI标注 #{i}: ID={color_id}, bbox=({x},{y},{w},{h})")
                            
                            # 确保边界框在图像内
                            if x < 0 or y < 0 or x + w > ti_img.shape[1] or y + h > ti_img.shape[0]:
                                print(f"    警告: TI标注 #{i} bbox超出图像边界，将进行裁剪")
                                
                            x = max(0, x)
                            y = max(0, y)
                            w = min(w, ti_img.shape[1] - x)
                            h = min(h, ti_img.shape[0] - y)
                            
                            if w <= 0 or h <= 0:
                                print(f"    跳过TI标注 #{i}: 裁剪后bbox无效 ({x, y, w, h})")
                                continue
                                
                            # 裁剪图像 - 不添加文本水印
                            crop_img = ti_img[y:y+h, x:x+w].copy()
                            print(f"    TI裁剪成功，尺寸: {crop_img.shape}")
                            
                            # 创建按ID组织的子文件夹
                            id_folder = os.path.join(t_crop_folder, f"id_{color_id}")
                            os.makedirs(id_folder, exist_ok=True)
                            
                            # 保存裁剪的图像到ID子文件夹中
                            out_name = f"frame_{frame_idx+1}_ti_id{color_id}_{i}.jpg"
                            output_path = os.path.join(id_folder, out_name)
                            cv2.imwrite(output_path, crop_img)
                            print(f"  保存TI截图: {out_name} - 尺寸: {crop_img.shape}")
                            
                            # 收集该截图的元数据
                            if color_id not in ti_id_metadata:
                                ti_id_metadata[color_id] = {}
                            
                            # 添加元数据，包括bbox和完整的帧元数据
                            metadata_entry = {
                                "frame": frame_idx,
                                "frame_path": ti_path,
                                "bbox": [x, y, w, h],
                                "modality": "TI",
                                "color_id": color_id,
                                "original_annotation": ann
                            }
                            
                            # 添加从metadata.json中解析的所有帧元数据
                            if ti_frame_metadata:
                                metadata_entry.update(ti_frame_metadata)
                            
                            ti_id_metadata[color_id][out_name] = metadata_entry
                            
                            crops_count += 1
                        except Exception as e:
                            print(f"  处理TI标注时出错: {str(e)}")
                            import traceback
                            print(traceback.format_exc())
            
            # 保存RGB ID元数据
            for color_id, metadata in rgb_id_metadata.items():
                id_folder = os.path.join(v_crop_folder, f"id_{color_id}")
                metadata_path = os.path.join(id_folder, "screenshots_metadata.json")
                
                # 如果已存在元数据文件，先读取后合并
                existing_metadata = {}
                if os.path.exists(metadata_path):
                    try:
                        with open(metadata_path, 'r', encoding='utf-8') as f:
                            existing_metadata = json.load(f)
                    except Exception as e:
                        print(f"  读取RGB ID {color_id}的元数据时出错: {str(e)}")
                
                # 合并现有元数据与新元数据
                existing_metadata.update(metadata)
                
                # 保存更新的元数据
                with open(metadata_path, 'w', encoding='utf-8') as f:
                    json.dump(existing_metadata, f, ensure_ascii=False, indent=2)
                print(f"  保存RGB ID {color_id}的元数据到 {metadata_path}")
            
            # 保存TI ID元数据
            for color_id, metadata in ti_id_metadata.items():
                id_folder = os.path.join(t_crop_folder, f"id_{color_id}")
                metadata_path = os.path.join(id_folder, "screenshots_metadata.json")
                
                # 如果已存在元数据文件，先读取后合并
                existing_metadata = {}
                if os.path.exists(metadata_path):
                    try:
                        with open(metadata_path, 'r', encoding='utf-8') as f:
                            existing_metadata = json.load(f)
                    except Exception as e:
                        print(f"  读取TI ID {color_id}的元数据时出错: {str(e)}")
                
                # 合并现有元数据与新元数据
                existing_metadata.update(metadata)
                
                # 保存更新的元数据
                with open(metadata_path, 'w', encoding='utf-8') as f:
                    json.dump(existing_metadata, f, ensure_ascii=False, indent=2)
                print(f"  保存TI ID {color_id}的元数据到 {metadata_path}")
            
            return crops_count
                
        except Exception as e:
            import traceback
            print(f"生成截图失败: {str(e)}")
            print(traceback.format_exc())
            return 0

    def _draw_annotations(self, img, annotations, modality):
        """在图像上绘制标注框"""
        import cv2
        
        for ann in annotations:
            color_id = ann.get("color_id")
            bbox = ann.get("bbox")
            
            if not color_id or not bbox or len(bbox) != 4:
                continue
            
            # 获取颜色
            color_str = self.colors_map.get(color_id, "#FFFFFF")
            bgr_color = self._hex_to_bgr(color_str)
            
            # 绘制矩形框
            x, y, w, h = map(int, bbox)
            cv2.rectangle(img, (x, y), (x + w, y + h), bgr_color, 2)
            
            # 绘制标签
            label = f"ID {color_id}"
            cv2.putText(img, label, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, bgr_color, 1)
    
    def _hex_to_bgr(self, hex_color):
        """将十六进制颜色转换为BGR格式"""
        hex_color = hex_color.lstrip('#')
        r = int(hex_color[0:2], 16)
        g = int(hex_color[2:4], 16)
        b = int(hex_color[4:6], 16)
        return (b, g, r)
    
    def stop(self):
        """停止线程"""
        self.stop_flag = True

    def _default_find_frame_folder(self, video_path):
        """默认的帧文件夹查找逻辑，作为备选"""
        try:
            import os
            
            # 基本路径解析
            video_dir = os.path.dirname(video_path)
            video_name = os.path.basename(video_path)
            file_name_no_ext = os.path.splitext(video_name)[0]
            
            # 尝试解析项目结构
            parts = video_dir.split(os.sep)
            project_idx = -1
            for i, part in enumerate(parts):
                if part == "Videos":
                    project_idx = i
                    break
                    
            if project_idx >= 0:
                # 找到项目根目录
                project_root = os.sep.join(parts[:project_idx])
                frames_dir = os.path.join(project_root, "Frames")
                
                # 视频相对路径
                rel_path = os.sep.join(parts[project_idx+1:])
                
                # 尝试直接匹配
                parts = file_name_no_ext.split('_')
                base_name = file_name_no_ext
                if len(parts) > 1 and parts[-1] in ['T', 'V']:
                    base_name = '_'.join(parts[:-1])
                
                # 尝试几种可能的匹配路径
                possible_paths = [
                    os.path.join(frames_dir, rel_path, base_name),
                    os.path.join(frames_dir, base_name),
                    os.path.join(frames_dir, rel_path)
                ]
                
                for path in possible_paths:
                    if os.path.exists(path):
                        # 检查是否包含T or V子文件夹
                        if os.path.exists(os.path.join(path, "T")) or os.path.exists(os.path.join(path, "V")):
                            return path
            
            return None
        except Exception as e:
            print(f"默认帧查找出错: {str(e)}")
            return None

class VideoProgressPanel(QWidget):
    # 添加去标注信号
    gotoAnnotation = pyqtSignal(str)
    
    def __init__(self, parent=None):
        super().__init__(parent)
        self.project_path = ""
        self.video_list = []
        self.colors_map = {
            1: "#FF0000",  # Red
            2: "#00FF00",  # Green
            3: "#0000FF",  # Blue
            4: "#FFFF00",  # Yellow
            5: "#00FFFF",  # Cyan
            6: "#FF00FF",  # Magenta
            7: "#FFA500",  # Orange
            8: "#800080",  # Purple
            9: "#A52A2A",  # Brown
            10: "#808080"  # Gray
        }
        self.screenshot_thread = None
        self.current_project = None
        self.videos_dir = ""
        self.frames_dir = ""
        self._extraction_threads = []
        
        # 初始化UI
        self._init_ui()
        
    def _init_ui(self):
        main_layout = QVBoxLayout()
        
        # 视频列表
        self.tree = QTreeWidget()
        self.tree.setHeaderLabels(["视频", "RGB标注", "TI标注", "RGB帧", "TI帧", "操作"])
        self.tree.setColumnWidth(0, 300)  # 加宽第一列
        
        # 上下文菜单
        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self.show_context_menu)
        
        main_layout.addWidget(QLabel("视频进度:"))
        main_layout.addWidget(self.tree)
        
        # 视频控制按钮组
        video_control_layout = QHBoxLayout()
        self.refresh_btn = QPushButton("刷新视频列表")
        self.refresh_btn.clicked.connect(self.refresh_video_list)
        self.open_rgb_folder_btn = QPushButton("打开RGB标注文件夹")
        self.open_rgb_folder_btn.clicked.connect(self.open_rgb_annotation_folder)
        self.open_ti_folder_btn = QPushButton("打开TI标注文件夹")
        self.open_ti_folder_btn.clicked.connect(self.open_ti_annotation_folder)
        
        # 添加"去标注"和"核验一致性"按钮
        self.goto_annotation_btn = QPushButton("去标注")
        self.goto_annotation_btn.clicked.connect(self.goto_annotation)
        self.verify_consistency_btn = QPushButton("核验一致性")
        self.verify_consistency_btn.clicked.connect(self.verify_id_consistency)
        
        video_control_layout.addWidget(self.refresh_btn)
        video_control_layout.addWidget(self.open_rgb_folder_btn)
        video_control_layout.addWidget(self.open_ti_folder_btn)
        video_control_layout.addWidget(self.goto_annotation_btn)
        video_control_layout.addWidget(self.verify_consistency_btn)
        main_layout.addLayout(video_control_layout)
        
        # ID管理区域
        id_group = QGroupBox("ID管理")
        id_layout = QVBoxLayout()
        
        # ID管理标题和加载按钮
        id_header = QHBoxLayout()
        id_header.addWidget(QLabel("标注ID列表"))
        self.load_id_btn = QPushButton("加载ID列表")  # 改名为"加载ID列表"
        self.load_id_btn.clicked.connect(self.load_id_list)  # 新方法名
        id_header.addWidget(self.load_id_btn)
        id_layout.addLayout(id_header)
        
        # ID列表 - 使用水平布局包含两个列表
        id_lists_layout = QHBoxLayout()
        
        # RGB ID列表
        rgb_id_layout = QVBoxLayout()
        rgb_id_layout.addWidget(QLabel("RGB ID:"))
        self.rgb_id_list = QListWidget()
        rgb_id_layout.addWidget(self.rgb_id_list)
        id_lists_layout.addLayout(rgb_id_layout)
        
        # TI ID列表
        ti_id_layout = QVBoxLayout()
        ti_id_layout.addWidget(QLabel("TI ID:"))
        self.ti_id_list = QListWidget()
        ti_id_layout.addWidget(self.ti_id_list)
        id_lists_layout.addLayout(ti_id_layout)
        
        id_layout.addLayout(id_lists_layout)
        
        # ID操作按钮
        id_actions_layout = QHBoxLayout()
        self.open_rgb_id_btn = QPushButton("打开RGB文件夹")
        self.open_rgb_id_btn.clicked.connect(self.open_rgb_id_folder)
        self.open_ti_id_btn = QPushButton("打开TI文件夹")
        self.open_ti_id_btn.clicked.connect(self.open_ti_id_folder)
        self.archive_id_btn = QPushButton("归档所选ID")
        self.archive_id_btn.clicked.connect(self.archive_selected_id)
        
        id_actions_layout.addWidget(self.open_rgb_id_btn)
        id_actions_layout.addWidget(self.open_ti_id_btn)
        id_actions_layout.addWidget(self.archive_id_btn)
        id_layout.addLayout(id_actions_layout)
        
        id_group.setLayout(id_layout)
        main_layout.addWidget(id_group)
        
        # 截图选项和按钮
        screenshot_group = QGroupBox("标注截图生成")
        screenshot_layout = QVBoxLayout()
        
        # 按钮
        buttons_layout = QHBoxLayout()
        self.generate_btn = QPushButton("生成标注截图")
        self.generate_btn.clicked.connect(self.generate_screenshots)
        buttons_layout.addWidget(self.generate_btn)
        
        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)
        
        screenshot_layout.addLayout(buttons_layout)
        screenshot_layout.addWidget(self.progress_bar)
        
        # 状态信息
        self.status_label = QLabel("准备就绪")
        screenshot_layout.addWidget(self.status_label)
        
        screenshot_group.setLayout(screenshot_layout)
        main_layout.addWidget(screenshot_group)
        
        self.setLayout(main_layout)
        
        # 连接信号
        self.tree.itemActivated.connect(self.open_video_folder)
    
    def _find_frame_folder(self, video_path):
        """根据视频路径找到对应的帧文件夹"""
        try:
            # 修正项目路径解析逻辑，确保包含子项目
            # 首先，确定Videos在当前项目中的位置
            video_dir = os.path.dirname(video_path)
            project_dir = None
            
            # 向上遍历，找到包含"Videos"目录的项目根目录
            check_dir = video_dir
            while check_dir and os.path.dirname(check_dir) != check_dir:  # 避免到达盘符根目录仍未找到
                parent_dir = os.path.dirname(check_dir)
                # 检查是否在parent_dir下存在Videos和Frames目录
                if (os.path.basename(check_dir) == "Videos"and
                    os.path.exists(os.path.join(parent_dir, "Frames"))):
                    project_dir = parent_dir
                    break
                check_dir = parent_dir
            
            if not project_dir:
                print(f"无法确定项目路径，从视频路径: {video_path}")
                # 回退到默认解析方式
                project_dir = os.path.abspath(os.path.join(os.path.dirname(video_path), "../.."))
            
            videos_dir = os.path.join(project_dir, "Videos")
            frames_dir = os.path.join(project_dir, "Frames")
            
            print(f"确定的项目路径: {project_dir}")
            print(f"视频目录: {videos_dir}")
            print(f"帧目录: {frames_dir}")
            
            # 获取视频相对于Videos目录的路径
            try:
                rel_path = os.path.relpath(video_path, videos_dir)
                video_dir = os.path.dirname(rel_path)
            except ValueError:
                # 如果视频不在Videos目录下，记录错误但继续尝试其他方法
                print(f"视频文件不在Videos目录内: {video_path}")
                video_dir = ""
                
            video_name = os.path.basename(video_path)
            file_name_no_ext = os.path.splitext(video_name)[0]
            
            # 从视频文件名提取关键部分用于匹配帧目录
            parts = file_name_no_ext.split('_')
            
            # 处理可能的模态后缀，移除最后的 _T  or  _V
            base_name = file_name_no_ext
            if len(parts) > 1 and parts[-1] in ('T', 'V'):
                base_name = '_'.join(parts[:-1])
                
            print(f"查找帧目录: 视频名={video_name}, 基础名={base_name}")
            
            # 1. 首先尝试最直接的匹配 (Frames/base_name)
            direct_match_path = os.path.join(frames_dir, base_name)
            print(f"尝试直接匹配: {direct_match_path}")
            
            if os.path.exists(direct_match_path) and self._is_valid_frame_dir(direct_match_path):
                print(f"找到直接匹配的目录: {direct_match_path}")
                return direct_match_path
            
            # 2. 尝试使用视频所在子目录路径 (如有)
            if video_dir:
                subdir_match_path = os.path.join(frames_dir, video_dir, base_name)
                print(f"尝试子目录匹配: {subdir_match_path}")
                if os.path.exists(subdir_match_path) and self._is_valid_frame_dir(subdir_match_path):
                    print(f"找到子目录匹配的目录: {subdir_match_path}")
                    return subdir_match_path
            
            # 3. 在整个Frames目录下搜索所有可能的匹配项
            print("开始全目录搜索...")
            for root, dirs, _ in os.walk(frames_dir):
                for dir_name in dirs:
                    # 检查是否是当前视频对应的文件夹
                    if dir_name == base_name:
                        test_path = os.path.join(root, dir_name)
                        if self._is_valid_frame_dir(test_path):
                            print(f"全目录搜索找到匹配: {test_path}")
                            return test_path
            
            # 4. 检查Frames目录下所有文件夹是否包含T or V子文件夹
            # 这是一个更宽松的搜索，可能找到更多潜在匹配项
            for root, dirs, _ in os.walk(frames_dir):
                if "T" in dirs or "V" in dirs:
                    print(f"发现包含T/V子目录的目录: {root}")
                    # 检查当前目录名称是否与视频名称相关
                    current_dir = os.path.basename(root)
                    
                    # 检查是否匹配视频名的一部分
                    match_score = 0
                    for part in parts:
                        if part in current_dir and len(part) > 3:  # 避免匹配太短的部分
                            match_score += 1
                    
                    # 如果匹配足够多的部分，认为找到了
                    if match_score > 0:
                        print(f"通过部分匹配找到目录(匹配度{match_score}): {root}")
                        return root
                    
                    # 如果基础名称是目录名的子串，也认为找到了
                    if base_name in current_dir:
                        print(f"通过子串匹配找到目录: {root}")
                        return root
            
            # 找不到任何匹配
            print(f"无法找到视频 {video_name} 对应的帧文件夹")
            return None
            
        except Exception as e:
            print(f"查找帧文件夹出错: {str(e)}")
            import traceback
            print(traceback.format_exc())
            return None

    def _init_context_menu(self):
        self.menu = QMenu()
        self.extract_action = self.menu.addAction("抽帧")
        self.extract_action.triggered.connect(self.start_extraction_for_selected)
        self.open_folder_action = self.menu.addAction("打开目录")
        self.open_folder_action.triggered.connect(self.open_selected_folder)
        self.screenshot_action = self.menu.addAction("生成标注截图")
        self.screenshot_action.triggered.connect(self.generate_screenshots_for_selected)
        # 添加修复标注文件结构的菜单项
        self.menu.addSeparator()
        self.fix_annotations_action = self.menu.addAction("修复标注文件结构")
        self.fix_annotations_action.triggered.connect(self.fix_annotations_structure)
        
    def show_context_menu(self, pos):
        if not hasattr(self, 'menu'):
            self._init_context_menu()
            
        item = self.tree.itemAt(pos)
        if item:
            self.menu.exec_(self.tree.viewport().mapToGlobal(pos))
    
    def start_extraction_for_selected(self):
        selected_items = self.tree.selectedItems()
        if not selected_items:
            return
            
        item = selected_items[0]
        video_path = item.data(0, Qt.UserRole)
        if video_path or os.path or exists(video_path):
            self.start_extraction(item, video_path, None)
    
    def open_selected_folder(self):
        selected_items = self.tree.selectedItems()
        if not selected_items:
            return
            
        item = selected_items[0]
        video_path = item.data(0, Qt.UserRole)
        if video_path and os.path.exists(video_path):
            folder_path = os.path.dirname(video_path)
            subprocess.Popen(f'explorer "{os.path.abspath(folder_path)}"', shell=True)
    
    def generate_screenshots_for_selected(self):
        selected_items = self.tree.selectedItems()
        if selected_items:
            self.generate_screenshots()
    
    def set_project(self, project_path):
        """设置当前项目路径并刷新视频列表"""
        self.project_path = project_path
        self.current_project = project_path
        self.videos_dir = os.path.join(project_path, "Videos")
        self.frames_dir = os.path.join(project_path, "Frames")
        self.refresh_video_list()

    def _fix_size_mismatches(self):
        """修复尺寸不匹配问题的实现"""
        # 这个方法将在点击"修复尺寸不匹配"按钮时调用
        # 显示一个对话框，说明将要执行的操作
        reply = QMessageBox.question(self, "确认修复", 
                                "此操作将尝试修复标注数量不匹配的问题，\n"
                                "建议先在标注工具中查看具体情况。\n\n"
                                "是否继续?",
                                QMessageBox.Yes | QMessageBox.No)
        
        if reply == QMessageBox.No:
            return
            
        # 由于这种修复需要在标注工具中进行，提示用户
        QMessageBox.information(self, "功能提示", 
                            "修复功能需要在标注工具中完成。\n"
                            "点击""去标注""按钮，然后使用标注工具的""检查一致性""功能。")
        
        # 跳转到标注工具
        self.goto_annotation()
     
    def refresh_video_list(self):
        """刷新视频列表和标注统计"""
        self.tree.clear()
        
        if not self.project_path:
            self.status_label.setText("未设置项目路径")
            return
            
        videos_dir = os.path.join(self.project_path, "Videos")
        if not os.path.exists(videos_dir):
            self.status_label.setText(f"未找到视频目录: {videos_dir}")
            return
        
        self.video_list = []
        
        # 遍历查找视频文件
        for root, _, files in os.walk(videos_dir):
            for file in files:
                if file.lower().endswith(VIDEO_EXTS):
                    video_path = os.path.join(root, file)
                    self.video_list.append(video_path)
        
        # 按组组织视频
        video_groups = defaultdict(list)
        for video_path in self.video_list:
            file_name = os.path.basename(video_path)
            parts = file_name.split('_')
            if len(parts) >= 4 and (parts[-1].lower().startswith(('t.', 'v.'))):
                group_key = '_'.join(parts[:3])
            else:
                group_key = os.path.splitext(file_name)[0]
            video_groups[group_key].append(video_path)
        
        # 添加到树视图
        for group_key, videos in video_groups.items():
            group_item = QTreeWidgetItem([group_key, "", "", "", "", ""])
            self.tree.addTopLevelItem(group_item)
            
            for video_path in videos:
                video_name = os.path.basename(video_path)
                
                # 确定视频类型（添加T或V前缀）
                prefix = ""
                if '_T.' in video_name.upper() or 'THERMAL' in video_name.upper():
                    prefix = "T: "
                elif '_V.' in video_name.upper() or 'RGB' in video_name.upper() or 'COLOR' in video_name.upper():
                    prefix = "V: "
                
                # 使用前缀添加到显示名称
                display_name = prefix + video_name
                
                rgb_annotations, ti_annotations, rgb_count, ti_count = self._analyze_video(video_path)
                
                # 创建项目并设置数据
                video_item = QTreeWidgetItem([
                    display_name, 
                    str(rgb_annotations), 
                    str(ti_annotations),
                    str(rgb_count), 
                    str(ti_count),
                    ""
                ])
                video_item.setData(0, Qt.UserRole, video_path)
                
                # 根据标注数量设置颜色
                if rgb_annotations > 0 or ti_annotations > 0:
                    for i in range(5):
                        video_item.setBackground(i, QColor(200, 255, 200))  # 浅绿色
                
                group_item.addChild(video_item)
                
                # 添加抽帧按钮
                self._add_extraction_button(video_item)
            
            # 展开组
            group_item.setExpanded(True)
        
        self.status_label.setText(f"找到 {len(self.video_list)} 个视频")
    
    def _add_extraction_button(self, item):
        """为视频项添加抽帧按钮"""
        video_path = item.data(0, Qt.UserRole)
        if not video_path:
            return
            
        # 解析视频路径获取信息，判断是否已抽帧
        frame_group_folder = self.find_frame_folder(video_path)
        video_name = os.path.basename(video_path)
        base_name = os.path.splitext(video_name)[0]
        parts = base_name.split('_')
        
        # 确定模态
        modality = None
        if len(parts) >= 1 and parts[-1] in ('T', 'V'):
            modality = parts[-1]
            
        # 如果未指定模态，尝试从文件名推断
        if not modality:
            if 'T.' in video_name.upper():
                modality = 'T'
            elif 'V.' in video_name.upper():
                modality = 'V'
                
        # 检查是否已抽帧
        has_extracted = False
        if frame_group_folder:
            if modality and os.path.exists(os.path.join(frame_group_folder, modality)):
                has_extracted = len(os.listdir(os.path.join(frame_group_folder, modality))) > 0
        
        # 创建按钮容器
        container = QWidget()
        layout = QHBoxLayout(container)
        layout.setContentsMargins(2, 2, 2, 2)
        layout.setSpacing(5)
        
        # 创建按钮
        extract_btn = QPushButton("重抽" if has_extracted else "抽帧")
        extract_btn.setFixedWidth(50)
        extract_btn.clicked.connect(lambda: self.start_extraction(item, video_path, None))
        
        layout.addWidget(extract_btn)
        layout.addStretch(1)
        
        # 设置到树项
        self.tree.setItemWidget(item, 5, container)
    
    def _analyze_video(self, video_path):
        """分析视频的标注数量和帧数，分别返回RGB标注数、TI标注数、RGB帧数、TI帧数"""
        rgb_annotations = 0
        ti_annotations = 0
        rgb_count = 0
        ti_count = 0
        
        try:
            # 找到对应的帧文件夹
            frame_group_folder = self._find_frame_folder(video_path)
            if not frame_group_folder:
                return 0, 0, 0, 0
                
            # 查找V and T子文件夹
            v_folder = os.path.join(frame_group_folder, "V")
            t_folder = os.path.join(frame_group_folder, "T")
            
            # 计算RGB and TI帧数
            if os.path.exists(v_folder):
                rgb_files = glob.glob(os.path.join(v_folder, "*.jpg")) + glob.glob(os.path.join(v_folder, "*.png"))
                rgb_count = len(rgb_files)
            
            if os.path.exists(t_folder):
                ti_files = glob.glob(os.path.join(t_folder, "*.jpg")) + glob.glob(os.path.join(t_folder, "*.png"))
                ti_count = len(ti_files)  # 修正变量名：t_files -> ti_files
            
            # 查找RGB标注文件 (V目录下)
            v_annotation_file = None
            if os.path.exists(os.path.join(v_folder, "annotations.json")):
                v_annotation_file = os.path.join(v_folder, "annotations.json")
                
            # 查找TI标注文件 (T目录下)
            t_annotation_file = None
            if os.path.exists(os.path.join(t_folder, "annotations.json")):
                t_annotation_file = os.path.join(t_folder, "annotations.json")
                
            # 读取并计算RGB标注数量
            if v_annotation_file:
                try:
                    with open(v_annotation_file, 'r', encoding='utf-8') as f:
                        data = json.load(f)
                        annotations = data.get("annotations", [])
                        rgb_annotations = len(annotations)
                except Exception as e:
                    print(f"读取RGB标注文件错误: {str(e)}")
            
            # 读取并计算TI标注数量
            if t_annotation_file:
                try:
                    with open(t_annotation_file, 'r', encoding='utf-8') as f:
                        data = json.load(f)
                        annotations = data.get("annotations", [])
                        ti_annotations = len(annotations)
                except Exception as e:
                    print(f"读取TI标注文件错误: {str(e)}")
        
        except Exception as e:
            print(f"分析视频出错: {str(e)}")
            
        if frame_group_folder:
            # 如果是当前选中的视频路径，自动加载ID列表
            selected_items = self.tree.selectedItems()
            if selected_items:
                selected_item = selected_items[0]
                if selected_item.parent() is None and selected_item.childCount() > 0:
                    selected_item = selected_item.child(0)
                    
                selected_video_path = selected_item.data(0, Qt.UserRole)
                if selected_video_path == video_path:
                    self.load_id_list()
                    
        return rgb_annotations, ti_annotations, rgb_count, ti_count

    def start_extraction(self, item, video_path, status_label=None):
        """开始抽帧处理"""
        
        # 先保存需要的所有数据
        item_info = {
            'video_path': video_path,
            'video_name': os.path.basename(video_path),
            'tree_item_row': self.tree.indexOfTopLevelItem(item) if item.parent() is None else -1,
            'parent_item_row': -1 if item.parent() is None else self.tree.indexOfTopLevelItem(item.parent()),
            'child_index': -1 if item.parent() is None else item.parent().indexOfChild(item)
        }
        
        # 提取视频相对于Videos后的真实子目录，并过滤掉重复的前缀层级
        rel_dir = ""
        if "Videos" in video_path:
            try:
                rel_part = video_path.split("Videos" + os.sep, 1)[1]
                rel_dir = os.path.dirname(rel_part)
                base = os.path.splitext(os.path.basename(video_path))[0]
                if '_' in base:
                    prefix = base.rsplit('_', 1)[0]
                    if rel_dir.endswith(prefix):
                        rel_dir = rel_dir[:-len(prefix)].rstrip(os.sep)
            except Exception as e:
                print(f"路径解析错误: {str(e)}")
                rel_dir = ""
        
        video_name = os.path.basename(video_path)
        base_name = os.path.splitext(video_name)[0]
        parts = base_name.split('_')
        
        # 确定模态
        modality = None
        if len(parts) >= 1:
            last_part = parts[-1]
            if last_part == 'T' or last_part == 'V':
                modality = last_part
                parent_folder = '_'.join(parts[:-1])
            else:
                parent_folder = base_name
        else:
            parent_folder = base_name
        
        # 输出的抽帧目录：Frames/（处理后的子目录）/parent_folder/模态
        extraction_base = os.path.join(self.frames_dir, rel_dir, parent_folder)
        
        # 如果未指定模态，尝试从文件名推断
        if not modality:
            if 'T.' in video_name.upper():
                modality = 'T'
            elif 'V.' in video_name.upper():
                modality = 'V'
            else:
                modality = 'Unknown'
                
        extraction_folder = os.path.join(extraction_base, modality)
        
        # 创建抽帧目录
        os.makedirs(extraction_folder, exist_ok=True)
        
        # 显示状态更新
        self.status_label.setText(f"正在抽取 {video_name} 到 {extraction_folder}...")
        
        # 更新按钮状态
        if self.tree.itemWidget(item, 5):
            btn = self.tree.itemWidget(item, 5).layout().itemAt(0).widget()
            if isinstance(btn, QPushButton):
                btn.setText("抽中")
                btn.setEnabled(False)
        
        # 启动抽帧线程
        thread = FrameExtractionThread(
            video_path,
            extraction_folder,
            DEFAULT_INTERVAL_SEC
        )
        
        # 添加到线程列表并启动
        self._extraction_threads.append(thread)
        thread.extraction_finished.connect(
            lambda success, msg, it=item: self.extraction_finished_callback(success, msg, item_info)
        )
        thread.start()
        
    def extraction_finished_callback(self, success, msg, item_info):
        """抽帧完成回调"""
        video_path = item_info['video_path']
        video_name = item_info['video_name']
        
        # 尝试找回树项（如果还存在）
        item = None
        try:
            if item_info['tree_item_row'] >= 0:
                # 顶级项目
                if item_info['tree_item_row'] < self.tree.topLevelItemCount():
                    item = self.tree.topLevelItem(item_info['tree_item_row'])
            elif item_info['parent_item_row'] >= 0 and item_info['child_index'] >= 0:
                # 子项目
                if item_info['parent_item_row'] < self.tree.topLevelItemCount():
                    parent = self.tree.topLevelItem(item_info['parent_item_row'])
                    if parent and item_info['child_index'] < parent.childCount():
                        item = parent.child(item_info['child_index'])
        except:
            item = None
        
        # 更新按钮状态（如果树项还存在）
        if item and self.tree.itemWidget(item, 5):
            btn = self.tree.itemWidget(item, 5).layout().itemAt(0).widget()
            if isinstance(btn, QPushButton):
                btn.setEnabled(True)
                btn.setText("重抽" if success else "抽帧")
        
        # 更新状态和视觉效果
        if success:
            self.status_label.setText(f"{video_name} 抽帧完成")
            # 只有当项目仍存在时才更新数据
            if item:
                rgb_annotations, ti_annotations, rgb_count, ti_count = self._analyze_video(video_path)
                item.setText(1, str(rgb_annotations))
                item.setText(2, str(ti_annotations))
                item.setText(3, str(rgb_count))
                item.setText(4, str(ti_count))
                
                # 如果有标注，更新颜色
                if rgb_annotations > 0 or ti_annotations > 0:
                    for i in range(5):
                        item.setBackground(i, QColor(200, 255, 200))  # 浅绿色
        else:
            self.status_label.setText(f"{video_name} 抽帧失败: {msg}")
            QMessageBox.critical(self, "抽帧错误", f"抽帧出错: {msg}")

    def open_video_folder(self, item, column):
        """双击打开视频所在文件夹，并加载ID列表"""
        video_path = item.data(0, Qt.UserRole)
        if video_path and os.path.exists(video_path):
            subprocess.Popen(f'explorer /select,"{os.path.abspath(video_path)}"', shell=True)
            # 加载该视频的ID列表
            self.load_id_list(video_path=video_path)
            
    def generate_screenshots(self):
        """生成标注截图"""
        # 检查是否有正在进行的线程
        if self.screenshot_thread and self.screenshot_thread.isRunning():
            QMessageBox.warning(self, "警告", "已有截图任务正在运行")
            return
            
        # 获取选中的视频
        selected_items = self.tree.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "警告", "请先选择一个视频")
            return
            
        item = selected_items[0]
        # 如果是顶级项目（组）且有子项目，使用第一个子项目
        if item.parent() is None and item.childCount() > 0:
            item = item.child(0)
            
        video_path = item.data(0, Qt.UserRole)
        if not video_path:
            QMessageBox.warning(self, "警告", "未找到视频路径")
            return

        # 修复这里 - 不再使用align_images_check
        align_images = False  # 默认不对齐图像
        
        # 创建并启动截图线程
        self.screenshot_thread = AnnotationScreenshotThread(
            video_path, self.colors_map,
            align_images=align_images,
            find_frame_folder_func=self._find_frame_folder
        )
        
        # 在启动前显示详细信息
        print(f"\n===== 开始生成标注区域截图 =====")
        print(f"视频路径: {video_path}")
        print(f"将处理所有有标注的帧")
        
        # 更改UI提示
        self.status_label.setText("正在生成标注区域截图...")
        
        # 连接信号
        self.screenshot_thread.progress_update.connect(self.update_progress)
        self.screenshot_thread.finished_signal.connect(self.on_screenshot_finished)
        self.screenshot_thread.error_signal.connect(self.on_screenshot_error)
        
        # 启动线程
        self.generate_btn.setText("取消")
        self.generate_btn.clicked.disconnect()
        self.generate_btn.clicked.connect(self.cancel_screenshot)
        self.progress_bar.setValue(0)
        self.status_label.setText("正在生成截图...")
        self.screenshot_thread.start()
        
    def cancel_screenshot(self):
        """取消截图线程"""
        if self.screenshot_thread and self.screenshot_thread.isRunning():
            self.screenshot_thread.stop()
            self.screenshot_thread.wait()  # 等待线程结束
            self.status_label.setText("已取消截图生成")
            
        # 恢复按钮
        self.generate_btn.setText("生成标注截图")
        self.generate_btn.clicked.disconnect()
        self.generate_btn.clicked.connect(self.generate_screenshots)
        
    def update_progress(self, current, total):
        """更新进度条"""
        progress = int(current * 100 / total) if total > 0 else 0
        self.progress_bar.setValue(progress)
        self.status_label.setText(f"正在处理: {current}/{total}")
        
    def on_screenshot_finished(self, message):
        """截图完成回调"""
        self.status_label.setText(message)
        QMessageBox.information(self, "完成", message)
        
        # 恢复按钮
        self.generate_btn.setText("生成标注截图")
        self.generate_btn.clicked.disconnect()
        self.generate_btn.clicked.connect(self.generate_screenshots)
        
    def on_screenshot_error(self, error_message):
        """截图错误回调"""
        self.status_label.setText(f"错误: {error_message}")
        QMessageBox.critical(self, "错误", error_message)
        
        # 恢复按钮
        self.generate_btn.setText("生成标注截图")
        self.generate_btn.clicked.disconnect()
        self.generate_btn.clicked.connect(self.generate_screenshots)
        
    def _is_valid_frame_dir(self, dir_path):
        """检查目录是否是有效的帧目录（包含T or V子文件夹）"""
        if not os.path.isdir(dir_path):
            return False
            
        # 检查是否存在T or V子目录
        has_valid_subdir = False
        
        for subdir in ["T", "V"]:
            subdir_path = os.path.join(dir_path, subdir)
            if os.path.isdir(subdir_path):
                # 查找是否有任何图像文件
                has_images = len(glob.glob(os.path.join(subdir_path, "*.jpg"))) > 0 or  \
                             len(glob.glob(os.path.join(subdir_path, "*.png"))) > 0
                if has_images:
                    print(f"验证有效帧目录: {dir_path} (包含{subdir}子文件夹且有图像)")
                    has_valid_subdir = True
        
        return has_valid_subdir
    
    # 兼容方法
    def find_frame_folder(self, video_path):
        """兼容方法，转发到_find_frame_folder"""
        return self._find_frame_folder(video_path)
    
    def fix_annotations_structure(self):
        """修复所有标注文件结构，将TI和RGB标注分离到各自目录"""
        if not self.project_path:
            QMessageBox.warning(self, "警告", "请先设置项目路径")
            return
            
        try:
            # 导入修复工具
            from core.tools.annotation_fixer import fix_annotations_structure
            
            # 询问用户确认
            reply = QMessageBox.question(self, "确认", 
                                        "此操作将检查项目中所有标注文件，\n"
                                        "将RGB和TI标注分离到各自对应的目录下。\n\n"
                                        "建议在操作前备份数据。是否继续？",
                                        QMessageBox.Yes | QMessageBox.No)
            
            if reply == QMessageBox.No:
                return
                
            # 执行修复
            self.status_label.setText("正在修复标注文件结构...")
            result = fix_annotations_structure(self.project_path)
            
            # 显示结果
            if "error" in result:
                QMessageBox.critical(self, "错误", result["error"])
                return
                
            message = f"处理完成!\n"
            message += f"- 总计处理: {result['processed']} 个目录\n"
            message += f"- 修复成功: {result['fixed']} 个标注文件\n"
            
            if result["errors"]:
                message += f"- 出现错误: {len(result['errors'])} 个\n"
                message += "  (详细错误信息请查看控制台输出)"
                
            # 详细信息输出到控制台
            print("\n===== 标注文件结构修复结果 =====")
            for detail in result["details"]:
                print(f"- 目录: {detail['dir']}")
                print(f"  源文件: {detail['source']}")
                print(f"  RGB标注: {detail['rgb_count']}, TI标注: {detail['ti_count']}")
                if detail.get("fixed"):
                    print(f"  状态: 已修复")
                else:
                    print(f"  状态: {detail.get('message', '未修改')}")
                print("")
                
            QMessageBox.information(self, "完成", message)
            
            # 修复完成后刷新视频列表
            self.refresh_video_list()
            self.status_label.setText("标注文件结构修复完成")
            
        except Exception as e:
            import traceback
            QMessageBox.critical(self, "错误", f"修复标注文件结构失败: {str(e)}")
            print(f"修复标注文件结构失败: {str(e)}")
            print(traceback.format_exc())
            self.status_label.setText("标注文件结构修复失败")

    def load_id_list(self, video_path=None):
        """加载ID列表，从当前选中视频的标注文件加载所有ID"""
        # 清空ID列表
        self.rgb_id_list.clear()
        self.ti_id_list.clear()
        
        # 如果未提供视频路径，则使用当前选中的视频
        if not video_path:
            selected_items = self.tree.selectedItems()
            if not selected_items:
                self.status_label.setText("未选择视频，请先选择一个视频")
                return
                
            item = selected_items[0]
            if item.parent() is None and item.childCount() > 0:
                # 如果是组，选择第一个子项
                item = item.child(0)
                
            video_path = item.data(0, Qt.UserRole)
            
        if not video_path:
            self.status_label.setText("所选项目不包含有效的视频路径")
            return
            
        # 查找该视频的帧文件夹
        frame_folder = self._find_frame_folder(video_path)
        if not frame_folder:
            self.status_label.setText("无法找到对应的帧文件夹")
            return
            
        # 查找标注文件和crop目录
        v_folder = os.path.join(frame_folder, "V")
        t_folder = os.path.join(frame_folder, "T")
        
        rgb_crop_folder = os.path.join(v_folder, "crop") if os.path.exists(v_folder) else None
        ti_crop_folder = os.path.join(t_folder, "crop") if os.path.exists(t_folder) else None
        
        # 加载RGB ID列表
        rgb_ids = []
        if rgb_crop_folder and os.path.exists(rgb_crop_folder):
            try:
                rgb_ids = [d.split('_')[1] for d in os.listdir(rgb_crop_folder) 
                          if os.path.isdir(os.path.join(rgb_crop_folder, d)) and d.startswith("id_")]
            except Exception as e:
                print(f"读取RGB ID列表出错: {str(e)}")
        
        # 加载TI ID列表
        ti_ids = []
        if ti_crop_folder and os.path.exists(ti_crop_folder):
            try:
                ti_ids = [d.split('_')[1] for d in os.listdir(ti_crop_folder) 
                         if os.path.isdir(os.path.join(ti_crop_folder, d)) and d.startswith("id_")]
            except Exception as e:
                print(f"读取TI ID列表出错: {str(e)}")
        
        # 添加到ID列表，按数字排序
        sorted_rgb_ids = sorted(rgb_ids, key=lambda x: int(x) if x.isdigit() else float('inf'))
        sorted_ti_ids = sorted(ti_ids, key=lambda x: int(x) if x.isdigit() else float('inf'))
        
        for id_str in sorted_rgb_ids:
            item = QListWidgetItem(f"ID {id_str}")
            item.setData(Qt.UserRole, id_str)
            self.rgb_id_list.addItem(item)
            
        for id_str in sorted_ti_ids:
            item = QListWidgetItem(f"ID {id_str}")
            item.setData(Qt.UserRole, id_str)
            self.ti_id_list.addItem(item)
        
        # 显示统计信息
        self.status_label.setText(f"已加载ID列表: RGB {len(rgb_ids)} 个, TI {len(ti_ids)} 个")

    def open_rgb_annotation_folder(self):
        """打开当前选中视频的RGB标注文件夹"""
        selected_items = self.tree.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "警告", "请先选择一个视频")
            return
            
        item = selected_items[0]
        # 如果是顶级项目（组）且有子项目，使用第一个子项目
        if item.parent() is None and item.childCount() > 0:
            item = item.child(0)
            
        video_path = item.data(0, Qt.UserRole)
        if not video_path:
            QMessageBox.warning(self, "警告", "未找到视频路径")
            return
        
        # 查找帧文件夹
        frame_folder = self._find_frame_folder(video_path)
        if not frame_folder:
            QMessageBox.warning(self, "警告", "无法找到帧文件夹")
            return
            
        # 查找RGB目录
        v_folder = os.path.join(frame_folder, "V")
        if not os.path.exists(v_folder):
            QMessageBox.warning(self, "警告", "未找到RGB帧文件夹")
            return
            
        # 打开文件夹
        try:
            os.startfile(v_folder)
        except:
            try:
                subprocess.Popen(f'explorer "{os.path.abspath(v_folder)}"', shell=True)
            except Exception as e:
                QMessageBox.warning(self, "错误", f"无法打开文件夹: {str(e)}")
    
    def open_ti_annotation_folder(self):
        """打开当前选中视频的TI标注文件夹"""
        selected_items = self.tree.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "警告", "请先选择一个视频")
            return
            
        item = selected_items[0]
        # 如果是顶级项目（组）且有子项目，使用第一个子项目
        if item.parent() is None and item.childCount() > 0:
            item = item.child(0)
            
        video_path = item.data(0, Qt.UserRole)
        if not video_path:
            QMessageBox.warning(self, "警告", "未找到视频路径")
            return
        
        # 查找帧文件夹
        frame_folder = self._find_frame_folder(video_path)
        if not frame_folder:
            QMessageBox.warning(self, "警告", "无法找到帧文件夹")
            return
            
        # 查找TI目录
        t_folder = os.path.join(frame_folder, "T")
        if not os.path.exists(t_folder):
            QMessageBox.warning(self, "警告", "未找到TI帧文件夹")
            return
            
        # 打开文件夹
        try:
            os.startfile(t_folder)
        except:
            try:
                subprocess.Popen(f'explorer "{os.path.abspath(t_folder)}"', shell=True)
            except Exception as e:
                QMessageBox.warning(self, "错误", f"无法打开文件夹: {str(e)}")

    def open_rgb_id_folder(self):
        """打开所选RGB ID文件夹"""
        selected_item = self.rgb_id_list.currentItem()
        if not selected_item:
            QMessageBox.warning(self, "警告", "请先选择一个RGB ID")
            return
        
        id_str = selected_item.data(Qt.UserRole)  # 获取ID
        self._open_specific_id_folder(id_str, modality="RGB")

    def open_ti_id_folder(self):
        """打开所选TI ID文件夹"""
        selected_item = self.ti_id_list.currentItem()
        if not selected_item:
            QMessageBox.warning(self, "警告", "请先选择一个TI ID")
            return
        
        id_str = selected_item.data(Qt.UserRole)  # 获取ID
        self._open_specific_id_folder(id_str, modality="TI")

    def _open_specific_id_folder(self, id_str, modality):
        """打开特定ID和模态的文件夹"""
        # 获取当前项目和帧文件夹
        selected_items = self.tree.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "警告", "请先选择一个视频")
            return
        
        item = selected_items[0]
        if item.parent() is None and item.childCount() > 0:
            item = item.child(0)
            
        video_path = item.data(0, Qt.UserRole)
        if not video_path:
            QMessageBox.warning(self, "警告", "无法获取视频路径")
            return
            
        # 查找帧文件夹
        frame_folder = self._find_frame_folder(video_path)
        if not frame_folder:
            QMessageBox.warning(self, "警告", "无法找到帧文件夹")
            return
            
        # 根据模态确定目录
        if modality == "RGB":
            v_folder = os.path.join(frame_folder, "V") 
            if os.path.exists(v_folder):
                target_folder = os.path.join(v_folder, "crop", f"id_{id_str}")
                if os.path.exists(target_folder):
                    try:
                        os.startfile(target_folder)
                    except:
                        subprocess.Popen(f'explorer "{os.path.abspath(target_folder)}"', shell=True)
                    return
                    
        elif modality == "TI":
            t_folder = os.path.join(frame_folder, "T") 
            if os.path.exists(t_folder):
                target_folder = os.path.join(t_folder, "crop", f"id_{id_str}")
                if os.path.exists(target_folder):
                    try:
                        os.startfile(target_folder)
                    except:
                        subprocess.Popen(f'explorer "{os.path.abspath(target_folder)}"', shell=True)
                    return
        
        QMessageBox.warning(self, "警告", f"未找到ID {id_str}的{modality}截图文件夹")

    def archive_selected_id(self):
        """归档所选ID到项目的IDs文件夹中"""
        # 检查是否在RGB或TI列表中选择了ID
        rgb_selected_item = self.rgb_id_list.currentItem()
        ti_selected_item = self.ti_id_list.currentItem()
        
        if not rgb_selected_item and not ti_selected_item:
            QMessageBox.warning(self, "警告", "请先选择一个RGB或TI的ID")
            return
        
        # 优先使用RGB的选中项
        if rgb_selected_item:
            color_id = rgb_selected_item.data(Qt.UserRole)
        else:
            color_id = ti_selected_item.data(Qt.UserRole)
        
        # 获取当前项目路径
        if not self.current_project:
            QMessageBox.warning(self, "警告", "未设置当前项目")
            return
            
        # 获取帧文件夹
        selected_items = self.tree.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "警告", "请先选择一个视频")
            return
            
        item = selected_items[0]
        if item.parent() is None and item.childCount() > 0:
            item = item.child(0)
            
        video_path = item.data(0, Qt.UserRole)
        if not video_path:
            QMessageBox.warning(self, "警告", "无法获取视频路径")
            return
            
        # 查找帧文件夹
        frame_folder = self._find_frame_folder(video_path)
        if not frame_folder:
            QMessageBox.warning(self, "警告", "无法找到帧文件夹")
            return
            
        # 验证项目结构
        ids_dir = os.path.join(self.current_project, "IDs")
        if not os.path.exists(ids_dir):
            try:
                os.makedirs(ids_dir)
            except Exception as e:
                QMessageBox.critical(self, "错误", f"创建IDs文件夹失败: {str(e)}")
                return
        
        # 查找RGB和TI截图文件夹
        v_folder = os.path.join(frame_folder, "V")
        t_folder = os.path.join(frame_folder, "T")
        
        rgb_crop_folder = os.path.join(v_folder, "crop", f"id_{color_id}") if os.path.exists(v_folder) else None
        ti_crop_folder = os.path.join(t_folder, "crop", f"id_{color_id}") if os.path.exists(t_folder) else None
        
        if not rgb_crop_folder and not ti_crop_folder:
            QMessageBox.warning(self, "警告", f"未找到ID {color_id}的任何截图文件夹")
            return
            
        # 改用更明确的对话框
        from PyQt5.QtWidgets import QPushButton, QDialog, QVBoxLayout, QLabel, QDialogButtonBox

        # 创建自定义对话框
        dialog = QDialog(self)
        dialog.setWindowTitle("选择归档方式")
        layout = QVBoxLayout(dialog)
        
        # 添加说明文本
        label = QLabel("请选择归档ID的方式：")
        layout.addWidget(label)
        
        # 创建按钮框
        button_box = QDialogButtonBox(QDialogButtonBox.NoButton, dialog)
        
        # 添加自定义按钮
        seq_button = QPushButton("顺序归档", dialog)
        spec_button = QPushButton("指定ID", dialog)
        cancel_button = QPushButton("取消", dialog)
        
        button_box.addButton(seq_button, QDialogButtonBox.AcceptRole)
        button_box.addButton(spec_button, QDialogButtonBox.ActionRole)
        button_box.addButton(cancel_button, QDialogButtonBox.RejectRole)
        
        layout.addWidget(button_box)
        dialog.setLayout(layout)
        
        # 连接按钮信号
        seq_button.clicked.connect(lambda: dialog.done(1))  # 顺序归档 = 1
        spec_button.clicked.connect(lambda: dialog.done(2))  # 指定ID = 2
        cancel_button.clicked.connect(lambda: dialog.done(0))  # 取消 = 0
        
        # 显示对话框并获取结果
        result = dialog.exec_()
        
        # 用户取消操作
        if result == 0:
            return
        
        target_id_path = ""
        
        if result == 1:  # 顺序归档
            # 查找项目中最大的ID编号
            existing_ids = [d for d in os.listdir(ids_dir) 
                           if os.path.isdir(os.path.join(ids_dir, d)) and d.isdigit()]
            if existing_ids:
                next_id = max(int(x) for x in existing_ids) + 1
            else:
                next_id = 1
            
            color_id = str(next_id)
            target_id_path = os.path.join(ids_dir, color_id)
        elif result == 2:  # 指定ID
            # 创建输入对话框
            from PyQt5.QtWidgets import QInputDialog
            input_id, ok = QInputDialog.getText(self, "指定目标ID", 
                                             "请输入要归档到的ID编号:")
            if not ok or not input_id.strip():
                return
                
            color_id = input_id.strip()
            target_id_path = os.path.join(ids_dir, color_id)
            
            # 如果目标ID已存在，确认是否合并
            if os.path.exists(target_id_path):
                reply = QMessageBox.question(self, "确认", 
                                           f"ID {color_id} 已存在。是否合并内容?",
                                           QMessageBox.Yes | QMessageBox.No)
                if reply == QMessageBox.No:
                    return
        
        # 创建ID目标文件夹
        rgb_target = os.path.join(target_id_path, "RGB")
        ti_target = os.path.join(target_id_path, "TI")
        
        try:
            if not os.path.exists(target_id_path):
                os.makedirs(target_id_path)
            if not os.path.exists(rgb_target):
                os.makedirs(rgb_target)
            if not os.path.exists(ti_target):
                os.makedirs(ti_target)
                
            # 复制RGB截图
            rgb_copied = 0
            if rgb_crop_folder and os.path.exists(rgb_crop_folder):
                rgb_images = [f for f in os.listdir(rgb_crop_folder) 
                            if f.lower().endswith(('.jpg', '.png', '.jpeg')) and not f.startswith('.')]
                
                metadata_file = os.path.join(rgb_crop_folder, "screenshots_metadata.json")
                has_metadata = os.path.exists(metadata_file)
                
                for img in rgb_images:
                    src_path = os.path.join(rgb_crop_folder, img)
                    dst_path = os.path.join(rgb_target, img)
                    if not os.path.exists(dst_path):
                        shutil.copy2(src_path, dst_path)
                        rgb_copied += 1
                        
                # 复制元数据文件
                if has_metadata:
                    dst_metadata = os.path.join(rgb_target, "screenshots_metadata.json")
                    if not os.path.exists(dst_metadata):
                        shutil.copy2(metadata_file, dst_metadata)
            
            # 复制TI截图
            ti_copied = 0
            if ti_crop_folder and os.path.exists(ti_crop_folder):
                ti_images = [f for f in os.listdir(ti_crop_folder) 
                            if f.lower().endswith(('.jpg', '.png', '.jpeg')) and not f.startswith('.')]
                
                metadata_file = os.path.join(ti_crop_folder, "screenshots_metadata.json")
                has_metadata = os.path.exists(metadata_file)
                
                for img in ti_images:
                    src_path = os.path.join(ti_crop_folder, img)
                    dst_path = os.path.join(ti_target, img)
                    if not os.path.exists(dst_path):
                        shutil.copy2(src_path, dst_path)
                        ti_copied += 1
                        
                # 复制元数据文件
                if has_metadata:
                    dst_metadata = os.path.join(ti_target, "screenshots_metadata.json")
                    if not os.path.exists(dst_metadata):
                        shutil.copy2(metadata_file, dst_metadata)
            
            # 显示结果
            QMessageBox.information(self, "归档成功", 
                                f"ID {color_id} 归档完成!\n"
                                f"- RGB截图: {rgb_copied} 个\n"
                                f"- TI截图: {ti_copied} 个\n"
                                f"保存路径: {target_id_path}")
                                
        except Exception as e:
            QMessageBox.critical(self, "错误", f"归档ID {color_id} 时出错: {str(e)}")
            import traceback
            print(f"归档ID错误: {traceback.format_exc()}")

    def goto_annotation(self):
        """跳转到标注工具并打开对应的帧文件夹（支持多模态子目录）"""
        print("\n===== 跳转到标注工具 =====")
        selected_items = self.tree.selectedItems()
        
        # 检查选择有效性
        if not selected_items:
            QMessageBox.warning(self, "警告", "请先选择一个视频")
            print("错误: 未选择任何视频")
            return

        # 获取有效项目
        item = selected_items[0]
        if item.parent() is None and item.childCount() > 0:
            print(f"选中的是组: {item.text(0)}，使用第一个子视频")
            item = item.child(0)
        
        # 获取路径数据
        video_path = item.data(0, Qt.UserRole)
        if not video_path:
            QMessageBox.warning(self, "警告", "无效的视频路径")
            print("错误: 视频路径为空")
            return
        
        print(f"原始视频路径: {video_path}")
        
        # 查找帧文件夹（新增多模态处理）
        frame_folder = self._find_frame_folder(video_path)
        print(f"基础帧文件夹: {frame_folder}")
        
        # 自动检测多模态子目录
        modality_subdirs = []
        if frame_folder and os.path.exists(frame_folder):
            # 检测T/V子目录
            modality_subdirs = [d for d in ["V", "T"] 
                            if os.path.exists(os.path.join(frame_folder, d))]
            print(f"检测到模态子目录: {modality_subdirs}")

        # 构建最终使用的帧路径（默认优先使用可见光V）
        final_frame_folder = None
        if modality_subdirs:
            if "V" in modality_subdirs:
                final_frame_folder = os.path.join(frame_folder, "V")
                print("默认选择可见光帧（V目录）")
            else:
                final_frame_folder = os.path.join(frame_folder, modality_subdirs[0])
                print(f"自动选择首个模态目录: {modality_subdirs[0]}")
        else:
            final_frame_folder = frame_folder
        
        # 路径格式化（统一斜杠）
        def format_path(path):
            return path.replace("\\", "/") if path else None
        
        # 修改 annotation_info 构建部分:
        annotation_info = {
            "video_path": format_path(video_path) if video_path and os.path.exists(video_path) else None,
            "base_frame_folder": format_path(frame_folder),
            "frame_folders": {
                mod: format_path(os.path.join(frame_folder, mod))
                for mod in modality_subdirs
            },
            "selected_modality": "V" if "V" in modality_subdirs else (modality_subdirs[0] if modality_subdirs else None)
        }

        # 修改校验逻辑，如果帧文件夹存在就不要强制要求视频文件存在
        error_messages = []
        if not video_path or not os.path.exists(video_path):
            # 降级为警告而不是错误
            print(f"警告: 视频文件不存在: {video_path}")
            # 只有在帧文件夹也不存在时才添加为错误
            if not os.path.exists(frame_folder):
                error_messages.append(f"视频文件不存在: {video_path}")

        if not os.path.exists(frame_folder):
            error_messages.append(f"基础帧文件夹不存在: {frame_folder}")
        elif not modality_subdirs:
            error_messages.append("帧文件夹中未找到T/V子目录")
        
        # 处理校验错误
        if error_messages:
            msg = "\n".join(error_messages)
            QMessageBox.warning(self, "路径错误", 
                            f"以下问题可能影响标注工具运行:\n{msg}")
            print(f"路径校验错误: {msg}")
            # 不终止流程，允许用户尝试修复
        
        # 转换为JSON
        import json
        json_data = json.dumps(annotation_info, ensure_ascii=False, indent=2)
        print("发送的标注信息结构:")
        print(json_data)
        
        # 发送信号
        print("正在发送gotoAnnotation信号...")
        self.gotoAnnotation.emit(json_data)
        
        # 状态反馈
        status_msg = []
        if annotation_info["video_path"]:
            status_msg.append(os.path.basename(annotation_info["video_path"]))
        if modality_subdirs:
            status_msg.append(f"模态: {', '.join(modality_subdirs)}")
        self.status_label.setText("已跳转: " + " | ".join(status_msg))
        
        print("===== 跳转处理完成 =====\n")
    
    def verify_id_consistency(self):
        """核验RGB和TI截图的一致性，根据screenshots_metadata.json文件进行详细分析"""
        selected_items = self.tree.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "警告", "请先选择一个视频")
            return
            
        item = selected_items[0]
        # 如果是顶级项目（组）且有子项目，使用第一个子项目
        if item.parent() is None and item.childCount() > 0:
            item = item.child(0)
            
        video_path = item.data(0, Qt.UserRole)
        if not video_path:
            QMessageBox.warning(self, "警告", "未找到视频路径")
            return
            
        # 查找帧文件夹
        frame_folder = self._find_frame_folder(video_path)
        if not frame_folder:
            QMessageBox.warning(self, "警告", "无法找到帧文件夹")
            return
            
        # 查找RGB和TI目录
        v_folder = os.path.join(frame_folder, "V")
        t_folder = os.path.join(frame_folder, "T")
        
        if not os.path.exists(v_folder) or not os.path.exists(t_folder):
            QMessageBox.warning(self, "警告", "未找到RGB或TI帧文件夹")
            return
            
        # 查找crop目录
        rgb_crop_folder = os.path.join(v_folder, "crop")
        ti_crop_folder = os.path.join(t_folder, "crop")
        
        if not os.path.exists(rgb_crop_folder) or not os.path.exists(ti_crop_folder):
            QMessageBox.warning(self, "警告", "未找到RGB或TI截图文件夹")
            return
            
        # 获取所有ID列表
        rgb_ids = []
        ti_ids = []
        
        try:
            rgb_ids = [d for d in os.listdir(rgb_crop_folder) if os.path.isdir(os.path.join(rgb_crop_folder, d)) and d.startswith("id_")]
            ti_ids = [d for d in os.listdir(ti_crop_folder) if os.path.isdir(os.path.join(ti_crop_folder, d)) and d.startswith("id_")]
        except Exception as e:
            QMessageBox.critical(self, "错误", f"读取ID列表出错: {str(e)}")
            return
            
        # 找出RGB有但TI没有的ID以及TI有但RGB没有的ID
        rgb_id_set = set(rgb_ids)
        ti_id_set = set(ti_ids)
        
        rgb_only_ids = rgb_id_set - ti_id_set
        ti_only_ids = ti_id_set - rgb_id_set
        common_ids = rgb_id_set & ti_id_set
        
        # 存储不一致信息
        id_issues = {}  # color_id -> {issues}
        
        # 检查每个共有ID的帧一致性
        for id_folder in common_ids:
            color_id = id_folder.split('_')[1]  # 提取"id_X"中的X
            rgb_id_path = os.path.join(rgb_crop_folder, id_folder)
            ti_id_path = os.path.join(ti_crop_folder, id_folder)
            
            # 加载元数据文件
            rgb_metadata_path = os.path.join(rgb_id_path, "screenshots_metadata.json")
            ti_metadata_path = os.path.join(ti_id_path, "screenshots_metadata.json")
            
            rgb_metadata = {}
            ti_metadata = {}
            
            if os.path.exists(rgb_metadata_path):
                try:
                    with open(rgb_metadata_path, 'r', encoding='utf-8') as f:
                        rgb_metadata = json.load(f)
                except Exception as e:
                    print(f"读取RGB元数据文件错误: {str(e)}")
                    
            if os.path.exists(ti_metadata_path):
                try:
                    with open(ti_metadata_path, 'r', encoding='utf-8') as f:
                        ti_metadata = json.load(f)
                except Exception as e:
                    print(f"读取TI元数据文件错误: {str(e)}")
            
            if not rgb_metadata and not ti_metadata:
                # 如果没有元数据文件，回退到旧的方法（基于文件名）
                frame_issues = self._check_consistency_by_filename(rgb_id_path, ti_id_path)
                if frame_issues:
                    id_issues[color_id] = frame_issues
                continue
            
            # 对于有元数据的情况，进行详细分析
            # 1. 首先按帧组织RGB和TI的标注
            rgb_frames = {}  # frame_idx -> [metadata entries]
            ti_frames = {}   # frame_idx -> [metadata entries]
            
            # 处理RGB元数据
            for filename, metadata in rgb_metadata.items():
                frame_idx = metadata.get("frame")
                if frame_idx is not None:
                    if frame_idx not in rgb_frames:
                        rgb_frames[frame_idx] = []
                    rgb_frames[frame_idx].append(metadata)
            
            # 处理TI元数据
            for filename, metadata in ti_metadata.items():
                frame_idx = metadata.get("frame")
                if frame_idx is not None:
                    if frame_idx not in ti_frames:
                        ti_frames[frame_idx] = []
                    ti_frames[frame_idx].append(metadata)
            
            # 2. 找出只在RGB或只在TI中标注的帧
            all_frames = set(rgb_frames.keys()) | set(ti_frames.keys())
            rgb_only_frames = set(rgb_frames.keys()) - set(ti_frames.keys())
            ti_only_frames = set(ti_frames.keys()) - set(rgb_frames.keys())
            
            # 初始化这个ID的问题集
            frame_issues = {
                "rgb_only_frames": sorted(list(rgb_only_frames)),
                "ti_only_frames": sorted(list(ti_only_frames)),
                "inconsistent_frames": []
            }
            
            # 3. 检查共有帧中标注数量是否一致
            for frame in all_frames - (rgb_only_frames | ti_only_frames):
                rgb_annotations = rgb_frames.get(frame, [])
                ti_annotations = ti_frames.get(frame, [])
                
                if len(rgb_annotations) != len(ti_annotations):
                    frame_issues["inconsistent_frames"].append({
                        "frame": frame,
                        "rgb_count": len(rgb_annotations),
                        "ti_count": len(ti_annotations)
                    })
            
            # 如果有任何问题，记录这个ID
            if frame_issues["rgb_only_frames"] or frame_issues["ti_only_frames"] or frame_issues["inconsistent_frames"]:
                id_issues[color_id] = frame_issues
        
        # 生成报告
        report, size_mismatch_report = self._generate_consistency_report(rgb_only_ids, ti_only_ids, id_issues)
        
        # 显示核验结果
        dialog = QDialog(self)
        dialog.setWindowTitle("核验一致性报告")
        layout = QVBoxLayout(dialog)
        
        # 创建选项卡界面
        tabs = QTabWidget()
        
        # 主要问题选项卡
        main_tab = QWidget()
        main_layout = QVBoxLayout(main_tab)
        main_text = QTextEdit()
        main_text.setReadOnly(True)
        main_text.setPlainText(report)
        main_layout.addWidget(main_text)
        tabs.addTab(main_tab, "主要问题")
        
        # 尺寸不匹配选项卡（如果有）
        if size_mismatch_report:
            size_tab = QWidget()
            size_layout = QVBoxLayout(size_tab)
            size_text = QTextEdit()
            size_text.setReadOnly(True)
            size_text.setPlainText(size_mismatch_report)
            size_layout.addWidget(size_text)
            tabs.addTab(size_tab, "尺寸不匹配")
        
        layout.addWidget(tabs)
        
        # 添加操作按钮
        btn_layout = QHBoxLayout()
        
        # 修复尺寸不匹配按钮（如果有尺寸问题）
        if size_mismatch_report:
            fix_size_btn = QPushButton("修复尺寸不匹配")
            fix_size_btn.clicked.connect(self._fix_size_mismatches)
            btn_layout.addWidget(fix_size_btn)
        
        # 关闭按钮
        close_btn = QPushButton("关闭")
        close_btn.clicked.connect(dialog.accept)
        btn_layout.addWidget(close_btn)
        
        layout.addLayout(btn_layout)
        dialog.setMinimumSize(700, 500)
        dialog.exec_()


    def _check_consistency_by_filename(self, rgb_folder, ti_folder):
        """根据文件名检查帧一致性（用于没有元数据的情况）"""
        # 获取RGB和TI的所有帧号
        rgb_frames = set()
        ti_frames = set()
        
        for img in os.listdir(rgb_folder):
            if not img.lower().endswith(('.jpg', '.png', '.jpeg')) or img.startswith('.'):
                continue
            # 提取帧号 "frame_{frame_num}_rgb_id..."
            try:
                frame_num = img.split('_')[1]
                rgb_frames.add(frame_num)
            except:
                continue
                
        for img in os.listdir(ti_folder):
            if not img.lower().endswith(('.jpg', '.png', '.jpeg')) or img.startswith('.'):
                continue
            # 提取帧号 "frame_{frame_num}_ti_id..."
            try:
                frame_num = img.split('_')[1]
                ti_frames.add(frame_num)
            except:
                continue
                
        # 检查是否有不一致的帧
        rgb_only_frames = rgb_frames - ti_frames
        ti_only_frames = ti_frames - rgb_frames
        
        if rgb_only_frames or ti_only_frames:
            return {
                "rgb_only_frames": sorted(list(rgb_only_frames)),
                "ti_only_frames": sorted(list(ti_only_frames)),
                "inconsistent_frames": []  # 仅基于文件名无法检测到数量不一致
            }
        else:
            return None

    def _generate_consistency_report(self, rgb_only_ids, ti_only_ids, id_issues):
        """生成一致性核验报告，并分离尺寸不匹配问题"""
        report = f"核验结果:\n\n"
        size_mismatch_report = f"尺寸不匹配问题详情:\n\n"
        has_size_issues = False
        
        # 1. 报告只在RGB或TI中存在的ID
        if rgb_only_ids:
            report += "RGB独有ID:\n"
            for id_folder in sorted(rgb_only_ids):
                color_id = id_folder.split('_')[1]
                report += f"- ID {color_id} 只有RGB标注，没有TI标注\n"
            report += "\n"
            
        if ti_only_ids:
            report += "TI独有ID:\n"
            for id_folder in sorted(ti_only_ids):
                color_id = id_folder.split('_')[1]
                report += f"- ID {color_id} 只有TI标注，没有RGB标注\n"
            report += "\n"
        
        # 2. 报告共有ID中的帧不一致情况（除了尺寸不匹配）
        if id_issues:
            report += "以下ID在RGB和TI中存在不一致:\n"
            for color_id, issues in sorted(id_issues.items()):
                report += f"\n- ID {color_id}:\n"
                
                if issues.get("rgb_only_frames"):
                    report += f"  RGB独有帧: "
                    for frame_idx in issues["rgb_only_frames"][:10]:
                        report += f"帧{frame_idx+1} "
                    if len(issues["rgb_only_frames"]) > 10:
                        report += f"... (共{len(issues['rgb_only_frames'])}个)"
                    report += "\n"
                    
                if issues.get("ti_only_frames"):
                    report += f"  TI独有帧: "
                    for frame_idx in issues["ti_only_frames"][:10]:
                        report += f"帧{frame_idx+1} "
                    if len(issues["ti_only_frames"]) > 10:
                        report += f"... (共{len(issues['ti_only_frames'])}个)"
                    report += "\n"
                
                # 3. 尺寸不匹配问题单独报告
                if issues.get("inconsistent_frames"):
                    has_size_issues = True
                    size_mismatch_report += f"ID {color_id} 的尺寸不匹配问题:\n"
                    for frame_issue in issues["inconsistent_frames"]:
                        frame_num = frame_issue['frame'] + 1  # 显示给用户时从1开始计数
                        size_mismatch_report += (f"  帧 {frame_num}: "
                                        f"RGB有{frame_issue['rgb_count']}个标注, "
                                        f"TI有{frame_issue['ti_count']}个标注\n")
                    size_mismatch_report += "\n"
        
        # 4. 总结
        inconsistent_ids = len(rgb_only_ids) + len(ti_only_ids) + len([id for id, issues in id_issues.items() 
                                                                if issues.get("rgb_only_frames") or issues.get("ti_only_frames")])
        
        if inconsistent_ids == 0:
            report += "\n所有ID在RGB和TI模态下都存在（没有缺失ID）!"
        else:
            report += f"\n总结: 发现 {inconsistent_ids} 个ID存在模态缺失或帧缺失问题。"
        
        # 在主报告中添加尺寸不匹配摘要
        size_mismatch_count = sum(1 for issues in id_issues.values() if issues.get("inconsistent_frames"))
        if size_mismatch_count > 0:
            report += f"\n\n另外，发现 {size_mismatch_count} 个ID存在标注数量不匹配问题。"
            report += "\n请查看\"尺寸不匹配\"选项卡了解详细信息。"
        
        return report, size_mismatch_report if has_size_issues else ""



